﻿/*
 * This file is part of MonoSettlers.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://opensettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;

namespace MonoSettlers
{
    public delegate void Procedure<T>(T inParam);

    public enum Direction : int
    {
        _000 = 0,
        _045 = 1,
        _090 = 2,
        _135 = 3,
        _180 = 4,
        _225 = 5,
        _270 = 6,
        _315 = 7,
    }

    /// <summary>
    /// Currently there are only settlers.
    /// </summary>
    public enum MovableType : int
    {
        Settler = 0,
    }

    public interface IPositionTracker
    {
        Direction Direction { get; }
        CyclePoint Position { get; }
        event DChangeHandler<IPositionTracker, CyclePoint> OnPositionChanged;
        event DChangeHandler<IPositionTracker, Direction> OnDirectionChanged;
    }

    /// <summary>
    /// Intended to provide some default behavior for all essentially non-movable objects,
    /// which still are renderable, like resources or buildings.
    /// </summary>
    public class PositionTracker : IPositionTracker
    {
        private Direction m_Direction;
        private CyclePoint m_Position;

        public Direction Direction
        {
            get { return m_Direction; }
            set
            {
                Direction old = m_Direction;

                m_Direction = value;

                if (OnDirectionChanged != null)
                    OnDirectionChanged(this, old, value);
            }
        }
        public CyclePoint Position
        {
            get { return m_Position; }
            set
            {
                bool hasListener = (OnPositionChanged != null);
                Point old = new Point();
                
                if(hasListener)
                    old = m_Position.ToPoint();

                m_Position = value;

                if (hasListener && ((old.X != value.XGrid) || (old.Y != value.YGrid)) )
                    OnPositionChanged(this, CyclePoint.FromGrid(old), value);
            }
        }

        public event DChangeHandler<IPositionTracker, CyclePoint> OnPositionChanged;
        public event DChangeHandler<IPositionTracker, Direction> OnDirectionChanged;
    }

    public interface IMovable : IPositionTracker
    {
        event Procedure<IMovable> OnStop;
        Boolean IsMoving { get; }
        Boolean HasJob { get; }
        Resource? Carrying { get; set; }

        void Stop();
    }

    /// <summary>
    /// To prevent System.Drawing dependency just for this structure.
    /// </summary>
    [Serializable]
    public struct Rectangle
    {
        [XmlAttribute]
        public int X;
        [XmlAttribute]
        public int Y;
        [XmlAttribute]
        public int Width;
        [XmlAttribute]
        public int Height;
    }

    /// <summary>
    /// To prevent System.Drawing dependency just for this structure.
    /// </summary>
    public struct Point
    {
        [XmlAttribute]
        public int X;
        [XmlAttribute]
        public int Y;

        public Point(int inX, int inY)
        {
            X = inX;
            Y = inY;
        }


        public static bool operator !=(Point inA, Point inB)
        {
            return (inA.X != inB.X) || (inA.Y != inB.Y);
        }

        public static bool operator ==(Point inA, Point inB)
        {
            return (inA.X == inB.X) && (inA.Y == inB.Y);
        }

        public override bool Equals(object obj)
        {
            if (obj is Point)
                return (Point)obj == this;
            else
                return false;
        }

        public override string ToString()
        {
            return "X: " + X.ToString().Padding(3) + "; Y: " + Y.ToString().Padding(3);
        }
    }

    /// <summary>
    /// A cycle point is a key element in deterministic, discretized space-time. Even if it provides
    /// double values for rendering, it internally uses exact integers to manage its "solid" state.
    /// </summary>
    public struct CyclePoint
    {
        /// <summary>
        /// The only deterministic way to change a cycle point, by raw cycle manipulation.
        /// </summary>
        public Int64 XCycles;
        /// <summary>
        /// The only deterministic way to change a cycle point, by raw cycle manipulation.
        /// </summary>
        public Int64 YCycles;

        public const Double CYCLE_MILLIS = 33;

        public static bool operator !=(CyclePoint inA, CyclePoint inB)
        {
            return (inA.XCycles != inB.XCycles) || (inA.YCycles != inB.YCycles);
        }

        public static bool operator ==(CyclePoint inA, CyclePoint inB)
        {
            return (inA.XCycles == inB.XCycles) && (inA.YCycles == inB.YCycles);
        }

        public override bool Equals(object obj)
        {
            if(obj is CyclePoint)
                return (CyclePoint)obj == this;
            else
                return false;
        }

        /// <summary>
        /// Returns the floored X offset in terms of grid cells.
        /// </summary>
        public Int32 XGrid
        {
            get { return (Int32)Math.Floor(XCycles / CYCLE_MILLIS); }
        }

        /// <summary>
        /// Returns the floored Y offset in terms of grid cells.
        /// </summary>
        public Int32 YGrid
        {
            get { return (Int32)Math.Floor(YCycles / CYCLE_MILLIS); }
        }

        /// <summary>
        /// Returns the double precision Y offset in terms of grid cells.
        /// </summary>
        public Double Y
        {
            get { return YCycles / CYCLE_MILLIS; }
        }

        /// <summary>
        /// Returns the double precision X offset in terms of grid cells.
        /// </summary>
        public Double X
        {
            get { return XCycles / CYCLE_MILLIS; }
        }

        /// <summary>
        /// Converts a grid position into a cycle point.
        /// </summary>
        public static CyclePoint FromGrid(Point inPoint)
        {
            return FromGrid(inPoint.X, inPoint.Y);
        }

        /// <summary>
        /// Converts a grid position into a cycle point.
        /// </summary>
        public static CyclePoint FromGrid(Double inX, Double inY)
        {
            CyclePoint result = new CyclePoint();

            result.XCycles = (Int64)Math.Ceiling(inX * CYCLE_MILLIS);
            result.YCycles = (Int64)Math.Ceiling(inY * CYCLE_MILLIS);

            return result;
        }

        /// <summary>
        /// Converts a cycle point into an integer grid position.
        /// </summary>
        public Point ToPoint()
        {
            return new Point(XGrid, YGrid);
        }

        /// <summary>
        /// Initializes a cycle point structure with raw cycle offsets. You should
        /// only use this when you know what you are doing ;).
        /// </summary>
        public CyclePoint(Int64 inXCycles, Int64 inYCycles)
        {
            XCycles = inXCycles;
            YCycles = inYCycles;
        }

        /// <summary>
        /// The only deterministic way to change a cycle point, by raw cycle manipulation.
        /// </summary>
        public CyclePoint AddCycleVector(Point inCycleVector)
        {
            return new CyclePoint(XCycles + inCycleVector.X, YCycles + inCycleVector.Y);
        }

        public override string ToString()
        {
            return String.Format("XCycles: {0} ({2:0.##}); YCycles: {1} ({3:0.##})", XCycles, YCycles, X, Y);
        }
    }
}
